/*
 * Copyright (c) 2021 HiSilicon (Shanghai) Technologies CO., LIMITED.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>

#include "hi_ext_util.h"
#include "mpp_help.h"
#include "aic_mng.h"
#include "strm_saver.h"

/**
    AIC Stream File Saver.
*/
struct StrmSaver {
    EvtMon *emon;
    SkPair chn;

    FILE *file; // 当前打开的用来存储stream的文件句柄
    bool fileErr; // 文件操作错误，后续将不再写文件

    // stream相关
    size_t frmId; // 收到的frame的ID
    PAYLOAD_TYPE_E codecType;
    int width;
    int height;

    // config by user
    char *filePfx; // 文件名前缀，含目录
    size_t queSize; // 环形队列的size
    size_t frmPerFile; // 每个文件最大存储的frame数
};

static inline void SaverClearStrm(StrmSaver* self)
{
    self->frmId = 0;
    self->codecType = PT_BUTT;
    self->width = -1;
    self->height = -1;
}

static int SaverOpenFile(StrmSaver* self)
{
    char stFilePath[PATH_MAX] = {0};
    const char *fileExt = "";

    if (self->codecType == PT_H264) {
        fileExt = ".h264";
    } else if (self->codecType == PT_H265) {
        fileExt = ".h265";
    } else if (self->codecType == PT_MJPEG) {
        fileExt = ".mjpeg";
    } else {
        fileExt = ".unkn";
    }

    HI_ASSERT(self->frmPerFile > 0);
    int id = self->frmId / self->frmPerFile % self->queSize;

    if (!realpath(self->filePfx, stFilePath)) {
        LOGI("fileName:%s, stFilePath:%s\n", self->filePfx, stFilePath);
        HI_ASSERT(0);
    }
    if (snprintf_s(stFilePath, sizeof(stFilePath), sizeof(stFilePath) - 1,
        "%s%04d%s", self->filePfx, id, fileExt) < 0) {
        HI_ASSERT(0);
    }
    LOGI("SaverOpenFile standard file path:%s\n", stFilePath);
    LOGI("SAVER: %s <= %d frames ...\n", stFilePath, self->frmPerFile);
    self->file = fopen(stFilePath, "wb");
    if (!self->file) {
        LOGE("open '%s' for 'wb' FAIL, err='%s, %d'\n", stFilePath, strerror(errno), errno);
        self->fileErr = true;
        return -1;
    }
    return 0;
}

static void SaverOnFrmChn(void* user, int fd, uint32_t evts)
{
    HI_ASSERT(user);
    StrmSaver *self = (StrmSaver*)user;
    HI_ASSERT(fd == self->chn.in);
    EvtChkRet(evts, FDE_IN, fd);
    VencFrm *frm = NULL;;

    if (FdReadMsg(fd, &frm, sizeof(VencFrm *)) != sizeof(VencFrm *)) { // need ensure by xiao
        return;
    }
    HI_ASSERT(frm);

    if (self->fileErr) {
        goto END;
    }
    if (!self->file && SaverOpenFile(self) < 0) {
        goto END;
    }

    if (VencFrmToFile(&frm->strm, self->file) < 0) {
        LOGE("stop writing file, for writing FAIL\n");
        fclose(self->file);
        self->file = NULL;
        self->fileErr = true;
        goto END;
    }

    self->frmId++;
    if (self->frmId % self->frmPerFile == 0) {
        fclose(self->file);
        self->file = NULL;
    }

    END:
        HI_ASSERT(frm);
        VencFrmDelRef(frm);
}

static void SaverOnStrmOff(void* user)
{
    LOGI("SAVER: on strm off\n");
    HI_ASSERT(user);
    StrmSaver *self = (StrmSaver*)user;

    if (self->file) {
        fclose(self->file);
        self->file = NULL;
    }
    SaverClearStrm(self);
}

static bool SaverOnStrmOn(void* user, int vencChn, PAYLOAD_TYPE_E codecType, int width, int height)
{
    LOGI("SAVER: on strm on, codec=%d, width=%d, height=%d\n", codecType, width, height);
    HI_ASSERT(user);
    StrmSaver *self = (StrmSaver*)user;

    if (self->codecType != PT_BUTT) {
        LOGW("onStrmOn without onStrmOff before\n");
        SaverOnStrmOff(user);
    }
    HI_ASSERT(!self->file);

    self->frmId = 0;
    self->codecType = codecType;
    self->width = width;
    self->height = height;
    return true;
}

static void SaverOnVencFrm(void* user, VencFrm* frm)
{
    HI_ASSERT(user && frm);
    StrmSaver *self = (StrmSaver*)user;

    VencFrmAddRef(frm);
    if (FdWriteMsg(self->chn.out, &frm, sizeof(VencFrm*)) != sizeof(VencFrm*)) { // need ensure by xiao
        LOGE("post frm to svc thrd FAIL\n");
        VencFrmDelRef(frm);
    }
}

static const IAicStrmUser G_SAVER_STRM_USER = {
    .OnStrmOn = SaverOnStrmOn,
    .OnStrmOff = SaverOnStrmOff,
    .OnVencFrm = SaverOnVencFrm,
    .OnVideoFrm = NULL,
};

int StrmSaverCreate(StrmSaver** saver, int queSize, int frmPerFile, const char* filePfx)
{
    HI_ASSERT(saver);
    HI_ASSERT(filePfx && *filePfx);
    HI_ASSERT(queSize > 0);
    HI_ASSERT(frmPerFile > 0);
    *saver = NULL;
    int ret;

    StrmSaver *self = (StrmSaver*)malloc(sizeof(*self));
    HI_ASSERT(self);
    if (memset_s(self, sizeof(*self), 0, sizeof(*self)) != EOK) {
        HI_ASSERT(0);
    }

    ret = EmCreate(&self->emon);
    HI_ASSERT(ret == 0);
    ret = SkPairCreate(&self->chn);
    HI_ASSERT(ret == 0);
    ret = EmAddFd(self->emon, self->chn.in, FDE_IN, SaverOnFrmChn, self);
    HI_ASSERT(ret == 0);

    self->file = NULL;
    self->fileErr = false;
    SaverClearStrm(self);

    self->filePfx = HiStrdup(filePfx);
    self->queSize = queSize;
    self->frmPerFile = frmPerFile;

    ret = EmStart(self->emon);
    HI_ASSERT(ret == 0);

    ret = (int)AicAddStrmUser(&G_SAVER_STRM_USER, self);
    HI_ASSERT(ret);

    *saver = self;
    LOGI("SAVER: started, queSize=%d, frmPerFile=%d, filePfx='%s'\n", self->queSize, self->frmPerFile, self->filePfx);
    return 0;
}

void StrmSaverDestroy(StrmSaver* self)
{
    HI_ASSERT(self);
    int ret;

    ret = (int)AicDelStrmUser(&G_SAVER_STRM_USER, self);
    HI_ASSERT(ret);

    HI_ASSERT(self->emon);
    ret = EmStop(self->emon);
    HI_ASSERT(ret == 0);

    bool res = SkPairValid(&self->chn);
    HI_ASSERT(res);
    ret = EmDelFd(self->emon, self->chn.in);
    HI_ASSERT(ret == 0);

    VencFrm *frm = NULL;
    while (FdReadMsg(self->chn.in, &frm, sizeof(VencFrm *)) == sizeof(VencFrm *)) { // need ensure by xiao
        VencFrmDelRef(frm);
    }
    SkPairDestroy(&self->chn);

    if (self->file) {
        fclose(self->file);
    }
    if (self->filePfx) {
        free(self->filePfx);
    }

    EmDestroy(self->emon);
    free(self);
}

